Skip to content

feat(wardroom): refuse an impossible brief before the spawn burns a run - #10

Merged
stoneevenson-biz merged 1 commit into
mainfrom
fm/guard-brief-preflight
Sep 1, 2026
Merged

stoneevenson-biz merged 1 commit into
mainfrom
fm/guard-brief-preflight

Conversation

@stoneevenson-biz

Copy link
Copy Markdown
Owner

Why

Three briefs written on 2026-08-28/29 specified work no crewmate could do. Each would have cost an entire crewmate run; the intake council caught all three, but only after a full cycle of three model lenses.

  1. "Move data/command-center-roadmap.md into docs/" — data/ is gitignored, so the file is not in a worktree at all, and a gitignored file cannot be deleted by a commit.
  2. "Fix the 68 stale briefs" — same cause; they live under data/ and are invisible to a crewmate.
  3. "Test the chain under FM_HOME=$(mktemp -d)" — fm-home-seed.sh leases from the live treehouse pool regardless of FM_HOME, so a "test" run silently leaks a durable lease into the captain's pool.

A fourth reached a live crewmate: a brief carrying the retired >> status redirect, whose reports the permission profile silently refused for ten minutes while the pane looked idle.

All four are structural — decidable from the brief text, the project's own ignore rules, and a list of known-hazardous commands, with no model in the loop. This is the same move the Quarterdeck already makes with fm_gates_classify, applied to intake.

What

bin/fm-preflight-lib.sh classifies; bin/fm-spawn.sh decides. The preflight runs after the brief-exists check and ahead of the intake council — it needs no model, and the council exempts scouts, which is exactly what defect 3 was. Nothing is created before a refusal: no pane, no worktree, no meta. Secondmates are exempt (their home is a firstmate home, where data/ and state/ are theirs to operate).

rule why it cannot be done
gitignored not in the worktree, and no commit can add, move or delete it
primary-checkout the permission profile denies that tree to crewmates
pool-lease fm-home-seed.sh / fm-spawn.sh lease from the live pool whatever FM_HOME says, and FM_HOME=$(mktemp -d) claims an isolation it cannot provide
status-redirect the report is silently discarded; the brief must teach bin/fm-status.sh

The refusal names the offender. One that only said "invalid brief" would cost another cycle to diagnose, which is half of what this saves:

======================== PREFLIGHT =========================
REFUSED: fm-spawn for task pfgit-k3 - the brief asks for work the crewmate
cannot see or safely touch. Each offender is named below.
  [gitignored] data/command-center-roadmap.md
      gitignored in <project>, so it does not exist in a crewmate's worktree
      and no commit can add, move or delete it - line 4
Fix the brief at: <path>
Captain bypass (loud, logged): FM_PREFLIGHT_OVERRIDE=1
===========================================================

Fail closed, not noisily wrong

False refusals would train everyone to reach for the override, and then the gate protects nothing. So:

  • /Users/x/firstmate-notes/a is not under /Users/x/firstmate — the character after the root must be a separator.
  • fm-spawn.sh.bak and my-fm-spawn.sh are not fm-spawn.sh.
  • A mention is not an invocation. "add a preflight to bin/fm-spawn.sh" is ordinary firstmate-on-itself work. Every invocation form must be written as code, because English can put "bash" or a ./ path directly before a script name while saying the opposite of run it — that class was found by review and is now a fixture.
  • git check-ignore is the authority for gitignored, without --no-index, so a tracked path some pattern matches still reads as visible.
  • A non-git project is not "cannot tell": nothing is hidden there, so that one rule is inapplicable and the rest still run.
  • The known false-refusal class — a brief that must cite an invisible path, or that mentions node_modules/ — is documented in the spec, and FM_PREFLIGHT_OVERRIDE=1 is the loud, logged way through.

Gates

  • gate-t1-brief-preflight-rules — one committed fixture per rule, refused with the offender named; clean.md and lookalike.md must pass, as must the real ship and scout scaffolds bin/fm-brief.sh writes. Those are what stop this becoming a gate nothing gets past.
  • gate-t1-brief-preflight-spawn-gate — fm-spawn refuses before anything is created, ahead of the wardroom, for ship and scout alike; clean work gets through; the override is loud; secondmates are exempt.

Both born red and proven so (first_observed_red stamped by a ledger verify against a neutered classifier), and both are LEDGER_MUTATE-sensitive.

Verification

  • tests/run-all.sh — 64 ran, 2 skipped, 1 failed. The one failure is fm-herdr-h5-live, a LIVE gate that needs a reachable herdr server; it fails identically on the untouched tree in this environment and this diff touches nothing in the herdr path.
  • gates/verify.sh — green:40 red:3. Two are the declared reds (gate-l2-loop-audit-level, m1-hook-registered); the third is the same gate-h5-herdr-live-roundtrip, which is recorded green in the ledger and goes red only in this environment.
  • bash bin/fm-gates-lib.sh . — OK, every gate acceptable.
  • shellcheck bin/*.sh tests/*.sh — clean.

Not in scope

bin/fm-intake.sh is not hooked (it would save the council's model calls, but changes the council's contract), and a promoted scout still bypasses this the same way it already bypasses the wardroom. Both are noted in the spec.

Three briefs written on 2026-08-28/29 specified work no crewmate could do -
moving a gitignored file, fixing briefs under gitignored data/, and "testing"
a pool-leasing command under FM_HOME=$(mktemp -d), which leaks a durable lease
into the captain's live pool. The intake council caught all three, but only
after a full cycle of three model lenses. A fourth reached a live crewmate: a
brief carrying the retired >> status redirect, whose reports the permission
profile silently refused for ten minutes while the pane looked idle.

All four are structural - decidable from the brief text, the project's own
ignore rules, and a list of known-hazardous commands, with no model in the
loop. bin/fm-preflight-lib.sh decides them, and bin/fm-spawn.sh consults it
after the brief-exists check and ahead of the council: nothing is created
before the refusal, and scouts are covered where the council exempts them.
Secondmates are exempt - their home is a firstmate home, where data/ and
state/ are theirs to operate.

The refusal names the offending path or command and says why; one that only
said "invalid brief" would cost another cycle to diagnose, which is half of
what this saves.

Fail closed, but not noisily wrong. A lookalike is not a match:
firstmate-notes is not under firstmate, fm-spawn.sh.bak is not fm-spawn.sh,
and a MENTION of a pool-leasing script is not an invocation of it - a brief
that asks a crewmate to change one of those scripts is ordinary work. Every
invocation form must be written as code, because English can put "bash" or a
./ path directly before a script name while saying the opposite of run it;
the FM_HOME=$(mktemp -d) half needs no code context and is the truer signal
for the third defect anyway. git is the authority for gitignored, so a
tracked path a pattern happens to match still reads as visible. The known
false-refusal class - a brief that must cite an invisible path - is
documented, and FM_PREFLIGHT_OVERRIDE=1 is the loud, logged way through.

Gates gate-t1-brief-preflight-rules and gate-t1-brief-preflight-spawn-gate,
with a committed fixture per rule plus a clean brief and a lookalike brief
that must pass - the two that stop this becoming a gate nothing gets past.
Spec: docs/specs/2026-08-31-brief-preflight.md.
@stoneevenson-biz
stoneevenson-biz merged commit 899f1de into main Sep 1, 2026
2 of 4 checks passed
@stoneevenson-biz
stoneevenson-biz deleted the fm/guard-brief-preflight branch September 1, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant